#pragma once

#include "index.hpp"
#include "util.hpp"
#include <algorithm>
#include <jsoncpp/json/json.h>

namespace ns_searcher
{
	struct InvertedElemPrint
	{ 
		uint64_t doc_id;//文档id
		int weight;//累加权重
		std::vector<std::string> words;
		InvertedElemPrint():doc_id(0), weight(0)
		{}
	};

	class Searcher
	{
		private:
			ns_index::Index* index; // 功系统进行查找的索引
		public:
			Searcher() {}
			~Searcher() {}
		public:
			void InitSearcher(const std::string& input)
			{
				// 1. 获取 index 单例对象
				index = ns_index::Index::GetInstance();
				std::cout << "获取 index 单例成功" <<  std::endl;
				// 2. 根据 index 对象建立索引
				index->BulidIndex(input);
				std::cout << "建立正排和倒排索引成功" << std::endl;
			}
		
		// query: 搜索关键字
		// json_string: 返回给用户浏览器的搜索结果
		void Search(const std::string& query, std::string* json_string)
		{
			// 1.[分词]：对 query 进行按照 searcher 的要求进行分词
			std::vector<std::string> words;
			ns_util::JiebaUtil::CutString(query, &words);
		
			// 2.[触发]：根据分词的各个词，进行 index 查找; query 分词后的词也需要忽略大小写 -- 转为小写 tolower
			//ns_index::InvertedList inverted_list_all; // 汇总所有的倒排索引节点
			
			std::vector<InvertedElemPrint> inverted_list_all;//存放所有经过去重之后的倒排索引节点
			std::unordered_map<uint64_t, InvertedElemPrint> tokens_map; //文件id作为key，进行去重

			
			for (std::string word : words)
			{
				boost::to_lower(word); // 大小写忽略

				ns_index::InvertedList* inverted_list = index->GetInveredList(word); // 根据 word 获取倒排拉链
				if (nullptr == inverted_list) // 没有倒排拉链
				{
					continue; // 没有就检测下一个 word
				}

				// // 汇总所有倒排拉链的倒排索引节点
				// // 当前这里有个不完美的地方：之后解决
				// inverted_list_all.insert(inverted_list_all.end(), inverted_list->begin(), inverted_list->end());
				for (const auto& elem : *inverted_list) 
				{
					auto& item = tokens_map[elem.doc_id]; // 根据文档 id 获取关键字集合
					item.doc_id = elem.doc_id;
					item.weight += elem.weight; // 累加权值
					item.words.push_back(elem.word); // 把当前关键字添加到集合中
				}
			}

			for(const auto &item : tokens_map)//遍历去重之后的map
			{ 
				inverted_list_all.push_back(std::move(item.second)); //插入 倒排拉链打印节点 到inverted_list_all
			}



			// 3.[合并排序]：汇总查找结果，根据相关性(weight)降序排序 
			// std::sort(inverted_list_all.begin(), inverted_list_all.end(), \
			// 		[](const ns_index::InvertedElem& e1, const ns_index::InvertedElem& e2){
			// 			return e1.weight > e2.weight;
			// });

			std::sort(inverted_list_all.begin(), inverted_list_all.end(), \
					[](const InvertedElemPrint& e1, const InvertedElemPrint& e2){
						return e1.weight > e2.weight;
			});


			// 4.[构建]：根据查找出来的结果，构建 json_string -- jsoncpp -- 完成序列化，反序列化 -- 这里我们就只序列化了，之后还要用 jsoncpp 在其他地方完成反序列化
			Json::Value root;
			for (auto& item : inverted_list_all) // weight大 --> 小
			{
				ns_index::DocInfo* doc = index->GetForwardIndex(item.doc_id); // 根据倒排索引节点的 doc_id，去查正排索引
				if (doc == nullptr)
				{
					continue;
				}
				Json::Value elem;
				elem["title"] = doc->title;
				// 原始版本，全部内容，不是摘要
				// elem["content"] = doc->content; // content 是文档去标签的全部内容,但我们要的是 content 的一部分（摘要）之后解决

				//item是经过去重之后的节点, 此时的words是个vector,直接使用words[0]获取摘要即可,0号下标的值一定存在
				elem["desc"] = GetDesc(doc->content, item.words[0]);
				elem["url"] = doc->url;

				// 排序过了，权值是从大到小来的，先遍历的是权重高的，按序追加即可，在 root 中 json 子对象的顺序，就是 sort 之后数组的顺序
				root.append(elem);
			}

			Json::FastWriter writer; 
			*json_string = writer.write(root); // 把序列化后的结果放到 json_string 中
			//std::cout << (*json_string) << std::endl;
		}

		// 在摘要中，也要有搜索关键字 word
		 std::string GetDesc(const std::string &html_content, const std::string& word)
		 {
			// 找到 word 在 html_content 中的首次出现，往前找 50 字节（如果前面没有 50 个，就从 begin 开始截取）
			// 往后找 100 字节（如果没有 100 字节，到 end 就行），截取出这部分内容

			const int prev_step = 50;
			const int next_step = 100;
			// 1. 找到 word 首次出现 

			// 在这里使用 find 是有问题的，因为 find 区分大小写，我们传过来的 word 是小写的
			// 建立倒排索引时，to_lower 过，倒排索引统一是用小写建立的
			// query 分词之后，去找倒排索引也是 to_lower 找的，拿着小写去找的，用小写来找小写，对应的倒排拉链才能被获取上来
			// 但是 html_content 是原网页内容，是不忽略大小写的
			// 所以 find word 的时候，word 是小写，就有可能 find 不到，所以要用 search 

			auto cmp = [](int x, int y){ return (std::tolower(x) == std::tolower(y)); };
			auto iter = std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), cmp);

			if(iter == html_content.end()) // 这种情况是不可能存在的，但是防御性的写一下
			{ 
				return "None1";
			}
			int pos = std::distance(html_content.begin(), iter);

			// 2. 获取 start, end 位置
			int start = 0;
			int end = html_content.size() - 1;
			// 如果之前有 50 个以上的字符，就更新开始位置
			// 没写 = ，因为等于的话，更新时， start 和 end 的值是不变的，所以没必要更新了，就不写 =
			if (pos - prev_step > start)
			{
				start = pos - prev_step;
			}

			if (pos + next_step < end)
			{
				end = pos + next_step;
			}
			
			// 3. 截取子串并返回
			if (start >= end) return "None2";
			std::string desc = html_content.substr(start, end - start);
    		desc += "...";
    		return desc;
		 }
	};
}
